/* FINCoS Framework
 * Copyright (C) 2013 CISUC, University of Coimbra
 *
 * Licensed under the terms of The GNU General Public License, Version 2.
 * A copy of the License has been included with this distribution in the
 * fincos-license.txt file.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version. This program is distributed in the hope that it will be
 * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See the GNU General Public License for more details.
 */


package pt.uc.dei.fincos.controller.gui;

import java.awt.Color;
import java.awt.Dialog;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.util.LinkedHashMap;
import java.util.Map.Entry;

import javax.swing.ImageIcon;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.UIManager;
import javax.swing.table.DefaultTableModel;

import pt.uc.dei.fincos.controller.ConnectionConfig;

/**
 * Form that allows editing the properties of a new or existing connection.
 *
 * @author  Marcelo R.N. Mendes
 */
public final class ConnectionDetail extends ComponentDetail {

    /** serial id. */
    private static final long serialVersionUID = 1L;

    /** The old connection configuration (when in edition mode). */
    private ConnectionConfig oldCfg;

    /** The parent component of this form. */
    private final Dialog parent;

    /** UI variables. */
    private JPopupMenu propsPop = new JPopupMenu();
    private JMenuItem addMenuItem = new JMenuItem("Add");
    private JMenuItem delMenuItem = new JMenuItem("Delete");

    /**
     * Creates new form ConnectionDialog.
     *
     * @param parent    parent component of this form
     * @param connCfg   the old connection configuration
     *                  (when in edition mode)
     */
    public ConnectionDetail(Dialog parent, ConnectionConfig connCfg) {
        super(null);
        this.parent = parent;
        initComponents();
        addListeners();
        cepAdapterRadioBtn.setSelected(true);

        if (connCfg != null) {
            this.oldCfg = connCfg;
            this.op = UPDATE;
            setTitle("Editing \"" + connCfg.getAlias() + "\"");
            fillProperties(connCfg);
            aliasField.setEditable(false);
        } else {
            this.op = INSERT;
            setTitle("Create new Connection");
            ((DefaultTableModel) this.propertiesTable.getModel()).addRow(new Object[] {null, null});
        }

        this.setModalityType(Dialog.DEFAULT_MODALITY_TYPE);
        this.setLocationRelativeTo(null);
    }

    /**
     * Fills the UI with the parameters of a connection configuration.
     *
     * @param connCfg   the connection configuration
     */
    public void fillProperties(ConnectionConfig connCfg) {
        aliasField.setText(connCfg.getAlias());
        aliasField.setCaretPosition(0);
        if (connCfg.getType() == ConnectionConfig.CEP_ADAPTER) {
            cepAdapterRadioBtn.setSelected(true);
            String engine = connCfg.getProperties().get("engine");
            customPropertyField.setText(engine);
        } else {
            jmsRadioBtn.setSelected(true);
            String cfName = connCfg.getProperties().get("cfName");
            customPropertyField.setText(cfName);
        }
        customPropertyField.setCaretPosition(0);
        DefaultTableModel model = (DefaultTableModel) propertiesTable.getModel();
        for (int i = 0; i < model.getRowCount(); i++) {
            model.removeRow(0);
        }

        for (Entry<String, String> e : connCfg.getProperties().entrySet()) {
            if (e.getKey().equals("engine") || e.getKey().equals("cfName")) {
                continue;
            }
            model.addRow(new Object[] {e.getKey(), e.getValue()});
        }
        if (model.getRowCount() == 0) {
            ((DefaultTableModel) this.propertiesTable.getModel()).addRow(new Object[] {null, null});
        }
        propertiesTable.setAutoCreateRowSorter(true);
    }



    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        connTypeRadioGroup = new javax.swing.ButtonGroup();
        aliasLbl = new javax.swing.JLabel();
        aliasField = new javax.swing.JTextField();
        typeLbl = new javax.swing.JLabel();
        cepAdapterRadioBtn = new javax.swing.JRadioButton();
        jmsRadioBtn = new javax.swing.JRadioButton();
        customPropertyField = new javax.swing.JTextField();
        customPropertyLbl = new javax.swing.JLabel();
        propertiesLbl = new javax.swing.JLabel();
        jScrollPane1 = new javax.swing.JScrollPane();
        propertiesTable = new javax.swing.JTable();
        cancelBtn = new javax.swing.JButton();
        okBtn = new javax.swing.JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);

        aliasLbl.setText("Alias:");

        aliasField.setToolTipText("unique identifier for this connection");

        typeLbl.setText("Type:");

        connTypeRadioGroup.add(cepAdapterRadioBtn);
        cepAdapterRadioBtn.setText("CEP Adapter");
        cepAdapterRadioBtn.setToolTipText("Direct connection with the CEP engine, using a custom-code adapter");

        connTypeRadioGroup.add(jmsRadioBtn);
        jmsRadioBtn.setText("JMS");
        jmsRadioBtn.setToolTipText("Communication through JMS provider");

        customPropertyField.setToolTipText("");

        customPropertyLbl.setText("CF Name:");
        customPropertyLbl.setToolTipText("name of the connection factory at the JNDI server");
        customPropertyLbl.setMaximumSize(new java.awt.Dimension(55, 14));
        customPropertyLbl.setMinimumSize(new java.awt.Dimension(55, 14));
        customPropertyLbl.setPreferredSize(new java.awt.Dimension(55, 14));

        propertiesLbl.setText("Properties:");

        propertiesTable.setModel(new javax.swing.table.DefaultTableModel(
            new Object [][] {

            },
            new String [] {
                "Name", "Value"
            }
        ));
        jScrollPane1.setViewportView(propertiesTable);

        cancelBtn.setText("Cancel");
        cancelBtn.setPreferredSize(new java.awt.Dimension(95, 30));

        okBtn.setText("OK");
        okBtn.setPreferredSize(new java.awt.Dimension(90, 30));

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addContainerGap()
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(aliasLbl)
                            .addComponent(typeLbl)
                            .addComponent(customPropertyLbl, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                        .addGap(24, 24, 24)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(aliasField, javax.swing.GroupLayout.PREFERRED_SIZE, 158, javax.swing.GroupLayout.PREFERRED_SIZE)
                            .addGroup(layout.createSequentialGroup()
                                .addComponent(cepAdapterRadioBtn)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                                .addComponent(jmsRadioBtn))
                            .addComponent(customPropertyField, javax.swing.GroupLayout.PREFERRED_SIZE, 158, javax.swing.GroupLayout.PREFERRED_SIZE)))
                    .addGroup(layout.createSequentialGroup()
                        .addGap(255, 255, 255)
                        .addComponent(cancelBtn, javax.swing.GroupLayout.PREFERRED_SIZE, 106, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addComponent(okBtn, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                    .addGroup(layout.createSequentialGroup()
                        .addContainerGap()
                        .addComponent(propertiesLbl))
                    .addGroup(layout.createSequentialGroup()
                        .addContainerGap()
                        .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 451, javax.swing.GroupLayout.PREFERRED_SIZE)))
                .addContainerGap(14, Short.MAX_VALUE))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
                    .addComponent(aliasField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(aliasLbl))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(typeLbl)
                    .addComponent(cepAdapterRadioBtn)
                    .addComponent(jmsRadioBtn))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(customPropertyField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(customPropertyLbl, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addGap(18, 18, 18)
                .addComponent(propertiesLbl)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 270, Short.MAX_VALUE)
                .addGap(18, 18, 18)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(okBtn, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(cancelBtn, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addContainerGap())
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void addListeners() {
        okBtn.setIcon(new ImageIcon("imgs/ok.png"));
        cancelBtn.setIcon(new ImageIcon("imgs/cancel.png"));

        cancelBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                dispose();
            }
        });

        okBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (validateFields()) {
                    if ((op == INSERT && !isUnique(aliasField.getText()))
                        || (op == UPDATE && !oldCfg.getAlias().equals(aliasField.getText())
                         && !isUnique(aliasField.getName()))) {
                        JOptionPane.showMessageDialog(null, "There is already a connection named \""
                                + aliasField.getText() + "\".", "Invalid Input",
                                JOptionPane.ERROR_MESSAGE);
                    } else {
                        String alias = aliasField.getText();
                        int type = cepAdapterRadioBtn.isSelected()
                                 ? ConnectionConfig.CEP_ADAPTER
                                 : ConnectionConfig.JMS;
                        LinkedHashMap<String, String> props = new LinkedHashMap<String, String>();
                        if (type == ConnectionConfig.CEP_ADAPTER) {
                            props.put("engine", customPropertyField.getText());
                        } else {
                            props.put("cfName", customPropertyField.getText());
                        }
                        for (int i = 0; i < propertiesTable.getRowCount(); i++) {
                            String name = (String) propertiesTable.getValueAt(i, 0);
                            String value = (String) propertiesTable.getValueAt(i, 1);
                            if (name == null || name.isEmpty()) {
                                continue;
                            }
                            props.put(name, value);
                        }
                        ConnectionConfig newCfg = new ConnectionConfig(alias, type, props);
                        if (parent instanceof ConnectionsDialog) {
                            switch (op) {
                            case UPDATE:
                                ((ConnectionsDialog) parent).updateConnection(oldCfg, newCfg);
                                break;
                            case INSERT:
                                ((ConnectionsDialog) parent).addConnection(newCfg);
                                break;
                            }
                        } else if (parent instanceof DriverDetail) {
                            try {
                                Controller_GUI.getInstance().addConnection(newCfg);
                                ((DriverDetail) parent).updateConnectionsList();
                            } catch (Exception e1) {
                                JOptionPane.showMessageDialog(null,
                                        "Could not create connection (" + e1.getMessage() + ").",
                                        "Error", JOptionPane.ERROR_MESSAGE);
                            }
                        } else if (parent instanceof SinkDetail) {
                            try {
                                Controller_GUI.getInstance().addConnection(newCfg);
                                ((SinkDetail) parent).updateConnectionsList();
                            } catch (Exception e1) {
                                JOptionPane.showMessageDialog(null,
                                        "Could not create connection (" + e1.getMessage() + ").",
                                        "Error", JOptionPane.ERROR_MESSAGE);
                            }
                        }

                        dispose();
                    }

                } else {
                    JOptionPane.showMessageDialog(null,
                            "One or more required fields "
                          + "were not correctly filled.",
                            "Invalid Input",
                            JOptionPane.ERROR_MESSAGE);
                }
            }
        });

        java.awt.event.ItemListener radioLsnr = new java.awt.event.ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                if (cepAdapterRadioBtn.isSelected()) {
                    customPropertyLbl.setText("Engine:");
                    customPropertyLbl.setToolTipText("name of the CEP engine");
                    customPropertyField.setToolTipText("name of the CEP engine");
                } else {
                    customPropertyLbl.setText("CF name:");
                    customPropertyLbl.setToolTipText("name of the connection factory at the JNDI server");
                    customPropertyField.setToolTipText("name of the connection factory at the JNDI server");
                }

            }
        };
        cepAdapterRadioBtn.addItemListener(radioLsnr);
        jmsRadioBtn.addItemListener(radioLsnr);

        propsPop.add(addMenuItem);
        propsPop.add(delMenuItem);
        propertiesTable.addMouseListener(new PopupListener(propsPop));

        addMenuItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                DefaultTableModel model = ((DefaultTableModel) propertiesTable.getModel());
                model.addRow(new Object[]{null, null});
                propertiesTable.requestFocusInWindow();
                propertiesTable.editCellAt(model.getRowCount() - 1, 0);
            }
        });

        delMenuItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                DefaultTableModel model = ((DefaultTableModel) propertiesTable.getModel());
                int selected = propertiesTable.convertRowIndexToModel(propertiesTable.getSelectedRow());
                if (selected != -1) {
                    if (model.getRowCount() == 1) {
                        propertiesTable.setValueAt(null, 0, 0);
                        propertiesTable.setValueAt(null, 0, 1);
                    } else {
                        model.removeRow(selected);
                    }

                }
            }
        });
    }

    /**
     * Checks unique constraint for a given connection name.
     *
     * @param alias     connection alias
     * @return          <tt>true</tt> if there is no other connection in the set with the alias
     *                  passed as argument, <tt>false</tt> otherwise.
     */
    protected boolean isUnique(String alias) {
        for (ConnectionConfig c : Controller_GUI.getInstance().getConnections()) {
            if (c.getAlias().equals(alias)) {
                return false;
            }
        }
        return true;
    }

    @Override
    protected boolean validateFields() {
        boolean ret = true;
        if (aliasField.getText() == null
                || aliasField.getText().isEmpty()) {
            aliasField.setBackground(INVALID_INPUT_COLOR);
                   ret = false;
               } else {
                   Color defaultColor = UIManager.getColor("TextField.background");
                   aliasField.setBackground(defaultColor);
               }
        return ret;
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JTextField aliasField;
    private javax.swing.JLabel aliasLbl;
    private javax.swing.JButton cancelBtn;
    private javax.swing.JRadioButton cepAdapterRadioBtn;
    private javax.swing.ButtonGroup connTypeRadioGroup;
    private javax.swing.JTextField customPropertyField;
    private javax.swing.JLabel customPropertyLbl;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JRadioButton jmsRadioBtn;
    private javax.swing.JButton okBtn;
    private javax.swing.JLabel propertiesLbl;
    private javax.swing.JTable propertiesTable;
    private javax.swing.JLabel typeLbl;
    // End of variables declaration//GEN-END:variables
}
